package ngat.oss;

import ngat.astrometry.*;
import ngat.instrument.*;
import ngat.util.*;
import ngat.phase2.*;
import ngat.phase2.nonpersist.*;

import jyd.dbms.*;
import jyd.storable.*;
import jyd.collection.*;

import java.util.*;
import java.io.*;
import java.text.*;

/**
 * Static methods used in the implementation of the RCS Scheduling algorithm.
 * Parameters can be altered remotely via various CTRL TransactionRequest types.
 * Weighting function parameters can be generated using a dialog in the control GUI.
 *
 * 
 * $Id: Scheduling.java,v 1.6 2007/10/11 09:01:14 snf Exp $
 *
 */

public class Scheduling extends ConfigurationCache {

    /** ISO 8601 Dateformat.*/
    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd 'T' HH:mm:ss");

    private static SimpleTimeZone UTC = new SimpleTimeZone(0, "UTC");
    // Function identification codes.

    public static final int HEIGHT_FN        = 0;   // Source altitude.
    public static final int TRANSIT_FN       = 1;   // Height/Transit height.   
    public static final int SLEW_FN          = 2;   // Slew time.
    public static final int RECONFIG_FN      = 3;   // Reconfiguration.
    public static final int LATENESS_FN      = 4;   // Nearness to expiry time.
    public static final int FAIRNESS_FN      = 5;   // Tag fairness function.
    public static final int TIME_ALLOC_FN    = 6;   // Proposal time allocation.
    public static final int MISSED_PERIOD_FN = 7;   // Monitor Group missed period.

    public static final int OSS_RCS_DATATRANSFER_TIME = 0;
    public static final int OSS_SCHEDULE_OVERHEAD_TIME = 1;
    public static final int CCD_FILTER_SINGLE_SLOT_ROTATION_TIME = 2;
    public static final int OBSERVATION_NOMINAL_SLEW_TIME = 3;
    public static final int OBSERVATION_NOMINAL_RECONFIG_TIME = 4;
    public static final int OBSERVATION_NOMINAL_ACQUIRE_TIME = 5;
    public static final int CCD_READOUT_TIME_OVERHEAD = 6;
    public static final int CCD_READOUT_TIME_VARIABLE = 7;
    public static final int MOSAIC_SLEW_TIME = 8;
    public static final int POSITION_ACCURACY = 9;

    private static final String[] weightFns = new String[] 
    {"height", "transit", "slew", "reconfig", "lateness", "fairness", "timealloc", "missedperiod" };

    public Scheduling() {
	super();
	essential.put("oss.rcs.datatransfer.time",         new Integer(OSS_RCS_DATATRANSFER_TIME));
	essential.put("oss.schedule.overhead.time",        new Integer(OSS_SCHEDULE_OVERHEAD_TIME));
	essential.put("ccd.filter.single.slot.rotation.time", new Integer(CCD_FILTER_SINGLE_SLOT_ROTATION_TIME));
	essential.put("observation.nominal.slew.time",     new Integer(OBSERVATION_NOMINAL_SLEW_TIME));
	essential.put("observation.nominal.reconfig.time", new Integer(OBSERVATION_NOMINAL_RECONFIG_TIME));
	essential.put("observation.nominal.acquire.time",  new Integer(OBSERVATION_NOMINAL_ACQUIRE_TIME));
	essential.put("ccd.readout.time.overhead",         new Integer(CCD_READOUT_TIME_OVERHEAD));
	essential.put("ccd.readout.time.variable",         new Integer(CCD_READOUT_TIME_VARIABLE));
	essential.put("mosaic.slew.time",                  new Integer(MOSAIC_SLEW_TIME));
	essential.put("position.accuracy",                 new Integer(POSITION_ACCURACY));
    
	sdf.setTimeZone(UTC);

    }

   


    /** 
     * True if the  named instrument is actually serviceable at this point.
     */ 
    private boolean instrumentCCDAvailable;
    
    public boolean getInstrumentCCDAvailable() { return instrumentCCDAvailable;}
    
    /** 
     * Time taken to transfer data between OSS and RCS over network.(secs)
     */
    private long ossRcsDatatransferTime;
    
    public long getOssRcsDatatransferTime() { return ossRcsDatatransferTime;}
    
    /** 
     * Time taken to run the scheduling algorithm.(secs)
      */   
    private long ossScheduleOverheadTime;
    
    public long getOssScheduleOverheadTime() { return ossScheduleOverheadTime;}
    
    /** 
     * Time for filterwheel to move one slot.(secs)
      */  
    private long ccdFilterSingleSlotRotationTime;

    public long getCcdFilterSingleSlotRotationTime() { return ccdFilterSingleSlotRotationTime;}

    /** 
     *  Nominal time used for slewing between observations. (secs).
     */    
    private long observationNominalSlewTime;

    public long getObservationNominalSlewTime() { return observationNominalSlewTime;}

    /** 
     * Nominal time used for reconfiguration between observations.(secs)
     */   
    private long observationNominalReconfigTime;

    public long getObservationNominalReconfigTime() { return observationNominalReconfigTime;}

    /** 
     * Nominal time used for acquisition of observation source.(secs)
     */    
    private long observationNominalAcquireTime;
    
    public long getObservationNominalAcquireTime() { return observationNominalAcquireTime;}
    
    /** 
     * Fixed length overhead in ccd readout.
     */   
    private long ccdReadoutTimeOverhead;
    
    public long getCcdReadoutTimeOverhead() { return ccdReadoutTimeOverhead;}
    
    /** 
     * Multiplier in ccd readout window/binning factor.
     */
    private long ccdReadoutTimeVariable;
    
    public long getCcdReadoutTimeVariable() { return ccdReadoutTimeVariable;}
    
    /** 
     * Nominal time for (short) slewing between mosaic offsets.(secs)
     */   
    private long mosaicSlewTime;
    
    public long getMosaicSlewTime() { return mosaicSlewTime;}
    
    /** 
     * Accuracy with which positions must be specified. 
     * (used by Position in calculating RA, dec from orbital elements 
     * in order to determine when cached values should be updated.
     */
    private double positionAccuracy;
    
    public double getPositionAccuracy() { return positionAccuracy;}
    
    // Sidereal Time Calculations.
    
    /** The current Local Sidereal Time. */
    public static double LST = 0.0;
    
    /** Sets the LST, this will be set from the RCS which gets LST from the 
     * Telescope status obj. and passes over via a CTRL_SetLST_Req(newlst). */
    public static void setLST(double in) { LST = in;}


    /** Computes a future LST for the Scheduling algorithm, it needs to predict the
     * altitudes of source objects over the course of their observation. This method
     * does NOT use computeLST() as it would reset the static LST value.
     * @param futureTime Time offset from 'now' in milliseconds. */ 
    public static double getLST(long futureTime) {
	return getLST() + futureTime*1.0027379/1000;
    }
    
    /** Get the current LST as an angle offset in rads. */
    public static double getLST() { return LST;}

    // ----------------
    // Time Constraint.
    // ----------------   
    private static long nextTimeConstraint = 0L;

    public static long  getNextTimeConstraint() { return nextTimeConstraint; }

    public static void  setNextTimeConstraint(long in) { nextTimeConstraint = in; }


    // ----------------- 
    // TABU List.
    // ----------------- 

    public static Map tabuList = new HashMap();

    public static void addTabuGroup(String groupPath, long until) {
	tabuList.put(groupPath, new Long(until));
    }

    /** Returns true if the group is tabu.*/
    public static boolean isTabu(String groupPath, long time) {
	// Not on list then OK
	if (! tabuList.containsKey(groupPath))
	    return false;

	// On list, has its time passed ?
	long until = ((Long)(tabuList.get(groupPath))).longValue();
	if (until > time)
	    return true;

	// Time has passed so re-activated
	return false;
    }

    /** Return tabu time for group or 0 if not on list.*/
    public static long getTabuTime(String groupPath) {
	if (tabuList.containsKey(groupPath))
	    return ((Long)(tabuList.get(groupPath))).longValue();
	return 0L;
    }

    // --------------------------
    // Currently executing group.
    // --------------------------

    public static Group currentGroup;

    public static long currentGroupStartTime;

    public static void setCurrentGroup(Group cg) { currentGroup = cg;}

    public static Group getCurrentGroup() {return currentGroup;}

    public static void setCurrentGroupStartTime(long ct) { currentGroupStartTime = ct;}

    public static long getCurrentGroupStartTime() { return currentGroupStartTime;}

    // -----------------
    // FixedGroup stuff.
    // -----------------    
    /** The Path of the next fixed Group in the database. Determines time constraints
     * for moveable group Scheduling. */
    public static String   nextFGroupPath = null;
    
    /** Time at which the next Fixed Group must be executed. */
    public static long     nextFGroupTime = 0;

    /** Position (RA,dec) of the next Fixed Group. */
    public static Position nextFGroupPosition = null;
    
    /** Set the next Fixed Group references. <br>
     * (path, time, Start Position). ## THE POSN IS FUDGED FOR NOW ##
     */
    public static void setNextFGroup(FixedGroup in) {
	if (in != null) {
	    nextFGroupPath     = in.getFullPath(); 
	    nextFGroupTime     = in.getFixedTime();
	    // There should only be one obs in the list.
	    if (in.getObservations().size() != 0) {
		/// Get the one and only obs, grab its target and get its position now !
	    }
	    nextFGroupPosition = new Position(0.0,0.0); // ### TEMP
	}
    }

    /** Get the DB Path of the next Fixed Group as a String. */
    public static String   getNextFGroupPath() { return nextFGroupPath;}
    
    /** Get the time when the next Fixed Group must be excuted. */
    public static long     getNextFGroupTime() { return nextFGroupTime;}

    /** Get the Position (RA, dec) of the next Fixed Group's first observation source.
     * Really, a FixedGroup should only have ONE source otherwise it is nort really 'fixed' */
    public static Position getNextFGroupPosition() { return nextFGroupPosition;}
    
    // ---------------------------------------------------------
    // START - NEW Fixed group stuff.
    // ---------------------------------------------------------

    public static Map fixedGroups = new HashMap();

    /** Return an iterator over the set of fixed groups remaining.*/
    public static Iterator listFixedGroups() {
	return fixedGroups.values().iterator();
    }

    /** Either modifies an existing or inserts a new fixed group.*/
    public static void modifyFixedGroup(FixedGroup fg) {
	long fixedTime = fg.getFixedTime();
	// we are not interested in FGs which were for more than 1 hour ago.
	if (fixedTime < System.currentTimeMillis() - 3600*1000L) {
	    //System.err.println("Skip fixed group time for:"+fg.getFullPath()+
	    //   " at "+sdf.format(new Date(fg.getFixedTime())));
	    return;
	}
	String path = fg.getFullPath();
	fixedGroups.put(path, new Long(fixedTime));
	
	System.err.println("Modifying fixed group time for:"+fg.getFullPath()+
			   " to "+sdf.format(new Date(fg.getFixedTime()))+
			   " Map contains "+fixedGroups.size()+" entries");
	
    }

    public static void removeFixedGroup(FixedGroup fg) {
	String path = fg.getFullPath();
	
	if (fixedGroups.containsKey(path)) {
	    fixedGroups.remove(path);
	    System.err.println("Removed fixed group: "+path);
	}

    }

    /** Find the next fixed group after now and at least delta in future (or past).
     */
    public static FixedGroup findNextFixedGroup(long now, long delta) {
	
	String earlyPath = null;
	long   earliest  = now + 10*86400*1000L;

	Iterator ig = fixedGroups.keySet().iterator();
	while (ig.hasNext()) {
	    String fgPath = (String)ig.next();
	    Long lt = (Long)fixedGroups.get(fgPath);
	    long time = lt.longValue();

	    // This is a valid time and it is earliest so far.
	    if ((time > now+delta) && (time < earliest)) {
		earlyPath = fgPath;
		earliest  = time;
	    }
	}

	// Did we find a valid FG ?
	
	if (earlyPath != null) {

	    // We expect the group to exist as the map should be modified whenever
	    // a group is changed/deleted/added by the OSSCommandImpl responsible.
	    Path     path     = new Path(earlyPath);
	    Proposal proposal = null;
	    try {
		proposal = OSS_Utilities.fetchProposal(path);
	    } catch (Exception e) {
		e.printStackTrace();	
	    }

	    if (proposal != null) {
		
		Group group = proposal.findGroup(path.getGroupByName());
		
		if (group instanceof FixedGroup) {
		    // should this always be done ?
		    setNextFGroup((FixedGroup)group); // cache it.
		    return (FixedGroup)group;
		}
	    }
	}

	// At this stage we have failed for some reason or other, so null.
	return null;

    }

    /** Check a FixedGroup is valid - i.e. we can actually do it.*/
    public static boolean checkValid(FixedGroup group) {
	
	long time = group.getFixedTime();

	Iterator it = group.listAllObservations();
	while (it.hasNext()) {	 
	    
	    Observation observation = (Observation)it.next(); 
	 
	    Source source = observation.getSource();
	    if (source == null)	{
		System.err.println("ChkFg::No target supplied");
		return false;
	    }
	    
	    Position target = source.getPosition();
	    double srcAz  = target.getAzimuth(time);
	    double srcAlt = target.getAltitude(time);

	    double domeLimit = ServerContext.getTelescopeParameters().getDomeLowLimit(); // UUGH !!

	    if (srcAlt < Math.toRadians(20.0)) { // use real dome limit?
		System.err.println("ChkFg::Fixed target too low: "+Position.toDegrees(srcAlt,3));
		return false;
	    }
	    
	    InstrumentConfig instrumentConfig = observation.getInstrumentConfig();
	    if (instrumentConfig == null) {
		System.err.println("ChkFg::No instrument config supplied");
		return false;
	    }
	    
	    // See if we recognise the instrument.
	    Instrument inst = TelescopeStatus.getInstrumentFor(instrumentConfig);	
	    
	    if (inst == null) {
		System.err.println("ChkFg::Instrument not known for:"+instrumentConfig.getClass().getName());
		return false;
	    }

	    if (! (inst.canBeConfigured(instrumentConfig))) {
		System.err.println("ChkFg::Invalid instrument config");
		return false;
	    }
	}
       
	return true;
	
    }

    // ---------------------------------------------------------
    // END - NEW Fixed group stuff.
    // ---------------------------------------------------------

    public static Map tagPriorities = new HashMap();
 
    public static void addTagPriorityNormalizationFactor(String tagName, double f) {
	tagPriorities.put(tagName, new Double(f));
    }
    
    public static double getTagPriorityNormalizationFactor(String tagName) {
	if (tagPriorities.containsKey(tagName))
	    return 0.0;
	try {
	    return ((Double)tagPriorities.get(tagName)).doubleValue();
	} catch (Exception e) {
	    return 1.0;
	}
    }

    // Solar Position.
    
    /** Inclination of the ecliptic. */
    public static final double sinInc = Math.sin(Math.toRadians(23.4));
    
    private static Position solarPosition = null;
    
    public static void      setSolarPosition(Position in) { solarPosition = in;}
    
    public static Position  getSolarPosition() { return solarPosition;}
    
    // Lunar Position. 
    
    private static Position lunarPosition = null;
    
    public static void      setLunarPosition(Position in) { lunarPosition = in;}
    
    public static Position  getLunarPosition() { return lunarPosition;}

    // Seeing predictor.    

    private static int seeingPrediction = Group.CRAP;

    public static void       setSeeingPrediction(int in) { seeingPrediction = in; }
    
    public static int        getSeeingPrediction() { return seeingPrediction; }

    // Actual seeing prediction at zenith in R band
    private static double correctedSeeingValue = 99.0;

    public static void       setCorrectedSeeingValue(double in) { correctedSeeingValue = in; }

    public static double     getCorrectedSeeingValue() { return correctedSeeingValue; }

    /** Return the seeing band for specified seeing.*/
    public static int determineSeeingBand(double value) {
	int myseeing = Group.POOR;
		
	if (value < 0.8) {
	    myseeing =Group.EXCELLENT;
	} else if
	      (value < 1.3) {
	    myseeing = Group.AVERAGE;
	} else if
	      (value < 3.0){
	    myseeing = Group.POOR;
	} else {
	    myseeing = Group.CRAP;
	}

	return myseeing;
    }

    // Photom predictor.    
    
    private static int photomPrediction = Group.CRAP;

    public static void       setPhotomPrediction(int in) { photomPrediction = in; }
    
    public static int        getPhotomPrediction() { return photomPrediction; }


    /**Current time methods ## these are temporary for Simulation puposes ##.
     * The current time will be got from System. */
    public static long now = 0L;
    
    public static void setNow(long in) { now = in;}

    public static long getNow() { return now;}
    
    public static void setCurrentTime(long in) { now = in;}
    
    public static long getCurrentTime() { return now;}

    // Scheduling List.
    private static int schedulableGroupCount = 0;

    private static boolean preschedule = false;

    private static long deltaTime = 0L;

    private static Vector schedulableGroupList = null;

    public static void addSchedulableGroup(ScheduleDescriptor desc) {
	schedulableGroupList.add(desc);
	schedulableGroupCount++;
    }

    public static int  getSchedulableGroupCount() { return schedulableGroupCount; }

    public static void resetSchedulableGroupList() { 
	schedulableGroupList = new Vector();
	schedulableGroupCount = 0; 
    }

    public static List getSchedulableGroupList() { return schedulableGroupList; }

    public static Iterator listSchedulableGroups() { return schedulableGroupList.iterator(); }

    public static void sortSchedulableGroupList() {
	final Comparator schedComparator = new Comparator() {
	    
	    public int compare(Object first, Object second) {
		ScheduleDescriptor sd1 = (ScheduleDescriptor)first;
		ScheduleDescriptor sd2 = (ScheduleDescriptor)second;
		double firstScore  = sd1.getScore();
		double secondScore = sd2.getScore();
		// Note: We are sorting in descending order.
		if (firstScore < secondScore) return 1;
		if (firstScore > secondScore) return -1;
		return 0;
	    }
	};
   	
	try {
	    Collections.sort(schedulableGroupList, schedComparator);
	    //System.err.println("OK: Sorted schedule list.");
	} catch (ClassCastException cx) {
	    System.err.println("CCX sorting schedule: "+cx);
	} catch (UnsupportedOperationException ux) {
	    System.err.println("UOX sorting schedule: "+ux);
	}
    }

    /** Cache various group metrics.*/
    public static Map metricsCache = new HashMap();

    /** Update the metrics for a group.*/
    public static void updateMetricsCache(Group group) {
	
	// here we will work out the maximum elevation for the group's target(s)
	// during the upcoming night period over next 24H.
	// NOTE probably should compute from prev localnoon to next localnoon
	// incase this is called during the evening i.e. we might have missed
	// the high point tonight already...
	double lon = ServerContext.getTelescopeParameters().getSiteLongitude();
	double lat = ServerContext.getTelescopeParameters().getSiteLatitude();
	Site site = new Site("scope", lat, lon);

	double maxElev = -99.0;
	double maxTransit = -99.0;
	long st = System.currentTimeMillis();
	long t = st;
	while (t < st + 24*3600*1000L) {
	    
	    Position sun = Astrometry.getSolarPosition(t);
	    double sunElev = sun.getAltitude(t, site);

	    Iterator iob = group.listAllObservations();
	    while (iob.hasNext()) {
		Observation obs = (Observation)iob.next();
		Source src = obs.getSource();
		if (src != null) {
		    Position target = src.getPosition();
		    double elev = target.getAltitude(t, site);
		    if (sunElev < Math.toRadians(-6.0)) {
			// night so this one counts
			if (elev > maxElev)
			    maxElev = elev;
		    }
		    // update transit anyway
		    if (elev > maxTransit)
			maxTransit = elev;
		}
	    }
	    t += 10*60*1000L; // 10 mins
	} // next timestep
	
	// got max elev, store it against the group id
	metricsCache.put(group.getFullPath(), new Double(maxElev));
	
	System.err.println("Compute group metric for: "+group.getName()+
			   " MaxEl: "+Position.toDegrees(maxElev,2)+
			   " MaxTrans: "+Position.toDegrees(maxTransit,2));

    }
    
    /** Return the cached metric value for a group.*/
    public static double getMetricsCacheValue(Group group) {
	if (metricsCache.containsKey(group.getFullPath())) 
	    return ((Double)metricsCache.get(group.getFullPath())).doubleValue();
	else
	    // return a massive value dont want divzero problems
	    return 0.5*Math.PI;
    }
    
    public static void    setPreschedule(boolean in) { preschedule = in; }

    public static boolean getPreschedule() { return preschedule; }

    public static void    setDeltaTime(long in) { deltaTime = in; }

    public static long    getDeltaTime() { return deltaTime; }

    // Schedule weighting function parameters and veto parameters.

    // 1. Source height weighting function parameters.
    // -----------------------------------------------
    public static WeightingParameters heightFnParams = null;
    
    public static void setHeightFnParams(WeightingParameters in) { heightFnParams = in;}
    
    public static WeightingParameters getHeightFnParams() { return heightFnParams;}
    
    /** Convenience method, evaluates the Height Fn using supplied 'height'. */
    public static double calculateHeightFn(double in) { return heightFnParams.evaluate(in);}


    // 2. Source transit height weighting function parameters.
    // -------------------------------------------------------
    public static WeightingParameters transitFnParams = null;

    public static void setTransitFnParams(WeightingParameters in) { transitFnParams = in;}

    public static WeightingParameters getTransitFnParams() { return transitFnParams;}

    /** Convenience method, evaluates the Transit Fn using supplied 'height/transit'. */
    public static double calculateTransitFn(double in) { return transitFnParams.evaluate(in);}
 

    // 3. Slew time weighting function parameters. 
    // -------------------------------------------
    public static WeightingParameters slewFnParams = null;
    
    public static void setSlewFnParams(WeightingParameters in) { slewFnParams = in;}

    public static WeightingParameters getSlewFnParams() { return slewFnParams;}
    
    /** Convenience method, evaluates the Slew Fn using the supplied 'slew-time'. */
    public static double calculateSlewFn(double in) { return slewFnParams.evaluate(in);}
    

    // 4. Lateness weighting function parameters. 
    // ------------------------------------------
    public static WeightingParameters latenessFnParams = null;
    
    public static void setLatenessFnParams(WeightingParameters in) { latenessFnParams = in;}

    public static WeightingParameters getLatenessFnParams() { return latenessFnParams;}
 
    /** Convenience method, evaluates the Lateness Fn using the supplied 'time-to-live'. */
    public static double calculateLatenessFn(double in) { return latenessFnParams.evaluate(in);}

    // 5. Reconfig weighting function parameters. 
    // ------------------------------------------
    public static WeightingParameters reconfigFnParams = null;
    
    public static void setReconfigFnParams(WeightingParameters in) { reconfigFnParams = in;}

    public static WeightingParameters getReconfigFnParams() { return reconfigFnParams;}
 
    /** Convenience method, evaluates the Reconfig Fn using the supplied 'reconfig-time'. */
    public static double calculateReconfigFn(double in) { return reconfigFnParams.evaluate(in);}


    // 6. Fairness weighting function parameters. 
    // ------------------------------------------
    public static WeightingParameters fairnessFnParams = null;
    
    public static void setFairnessFnParams(WeightingParameters in) { fairnessFnParams = in;}

    public static WeightingParameters getFairnessFnParams() { return fairnessFnParams;}
 
    /** Convenience method, evaluates the Fairness Fn using the supplied 'time fraction'. */
    public static double calculateFairnessFn(double in) { return fairnessFnParams.evaluate(in);}

    // 7. Time alloc weighting function parameters. 
    // ------------------------------------------
    public static WeightingParameters timeAllocFnParams = null;
    
    public static void setTimeAllocFnParams(WeightingParameters in) { timeAllocFnParams = in;}

    public static WeightingParameters getTimeAllocFnParams() { return timeAllocFnParams;}
 
    /** Convenience method, evaluates the time Alloc Fn using the supplied 'time fraction'. */
    public static double calculateTimeAllocFn(double in) { return timeAllocFnParams.evaluate(in);}

    // 8. Monitor Group missed period weighting function parameters. 
    // ------------------------------------------
    public static WeightingParameters missedPeriodFnParams = null;
    
    public static void setMissedPeriodFnParams(WeightingParameters in) { missedPeriodFnParams = in;}

    public static WeightingParameters getMissedPeriodFnParams() { return missedPeriodFnParams;}
 
    /** Convenience method, evaluates the time Alloc Fn using the supplied 'time fraction'. */
    public static double calculateMissedPeriodFn(double in) { return missedPeriodFnParams.evaluate(in);}



    public void mergeWeights(File propertyFile) throws IllegalArgumentException {
	ConfigurationProperties weights = new ConfigurationProperties();	
	try {
	    System.out.println("Sched::mergeweights: entered with Propfile "+propertyFile);
	    weights.load(new FileInputStream(propertyFile));
	} catch (IOException e) {
	    throw new IllegalArgumentException("Error loading weighting function parameters: "+e);
	}
	for (int  i = 0; i < weightFns.length; i++) {
	    System.out.println("Sched::mergeweights: "+i);
	    mergeWeightFn(i, weights);
	}
    }

    /** Merge the weights for a given (named) schedule weighting function. */
    public void mergeWeightFn(int fnIndex, ConfigurationProperties weights) throws IllegalArgumentException {
	if (fnIndex < 0 || fnIndex >= weightFns.length)
	    throw new IllegalArgumentException("Weight fn-index out of range: ["+fnIndex+"] min:0, max:4");
	String fnName = weightFns[fnIndex];
	WeightingParameters wp = null;
	try {
	    int count = weights.getIntValue(fnName+".fn.anchor.count");
	    double x[] = new double[count];
	    double y[] = new double[count];
	    double scale = weights.getDoubleValue(fnName+".fn.scale");
	    double anchorX;
	    double anchorY;
	    for (int i = 0; i < count; i++) {
		anchorX = weights.getDoubleValue(fnName+".fn.anchor."+i+".x");
		x[i] = anchorX*scale;
		anchorY = weights.getDoubleValue(fnName+".fn.anchor."+i+".y");
		y[i] = anchorY;
	    }
	    double beta       = weights.getDoubleValue(fnName+".fn.weight");
	    String units      = weights.getProperty(fnName+".fn.units");
	    double vetoLow    = weights.getDoubleValue(fnName+".fn.veto.low.limit")*scale;
	    double vetoHi     = weights.getDoubleValue(fnName+".fn.veto.hi.limit")*scale;
	    boolean inclusive = weights.getBooleanValue(fnName+".fn.veto.masktype");
	    
	    wp = new WeightingParameters(x, y, beta, vetoLow, vetoHi, inclusive);	
	   
	} catch (ParseException e) {
	    throw new IllegalArgumentException("Error loading "+fnName+" weighting function: \n-("+e+")");
	} catch (NullPointerException e) { 
	    throw new IllegalArgumentException("Error loading "+fnName+" weighting function: \n-("+e+")");
	}
	// Make the weighting function.
	switch (fnIndex) {
	case HEIGHT_FN:
	    setHeightFnParams(wp);
	    break;
	case TRANSIT_FN:
	    setTransitFnParams(wp);
	    break;
	case SLEW_FN:
	    setSlewFnParams(wp);
	    break;
	case RECONFIG_FN:
	    setReconfigFnParams(wp);
	    break;
	case LATENESS_FN:
	    setLatenessFnParams(wp);
	    break;
	case FAIRNESS_FN:
	    setFairnessFnParams(wp);
	    break;
	case TIME_ALLOC_FN:
	    setTimeAllocFnParams(wp);
	    break;
	case MISSED_PERIOD_FN:
	    setMissedPeriodFnParams(wp);
	    break;
	default:
	    throw new IllegalArgumentException("Error loading weighting function - (UFO) "+fnIndex);
	}
	System.out.println("*****************Loaded weights for : "+fnName);

    }

     /** This method should be called whenever a Group's structure is 'significantly'
     * changed - e.g. when the Group is updated by a PEST user via PROPOSAL_UPDATE.
     * It calculates, using per-Observation and per-Source information about
     * the exposure lengths, number of mosaics, slews, reconfigs etc the nominal and
     * total (maximum) execution times. These represent respectively the time which
     * a PI uses to calculate the proposal time allocation for this Group and the
     * maximum possible time this Group will actually take - assuming such things as
     * mount unwraps, maximal initial and final slews. We use the nominal exec time
     * when the Proposal and Tag used-time are updated when the Group completes. We
     * use the maximal exec time to check the Group against any preset limits on 
     * available time set either by the OSS or RCS. 
     * NOTE: This method should only be called from within a DB transaction.
     */
    public static void calculateExecutionTimes(Group group) {
	
	int mosaic     = 0; 
	int multruns   = 1;
	int countObs   = 0; // count obs.
	int countMoves = 0; // counts distinct source moves.
	
	Observation observation = null;	   
	Source      source      = null;	
	Source  prevSource      = null; 	
	Position    target      = null;
	Position    prevTarget  = null;

	Instrument inst = null;
	InstrumentConfig instConfig = null;

	double maximalObserveTime = 0.0;
	double nominalObserveTime = 0.0;

	double slewTime = 0.0;
	double maximalExecutionTime = 0.0;
	double nominalExecutionTime = 0.0;


	// check for sequencing here and create it if it doesnt exist...
	Vector v = null;
	v = group.getSequence();
	System.err.println("GT::CrtTaskList::Group sequence is: "+v);
	if (v == null || v.size() == 0) {
	    v = new Vector();
	    Iterator it = group.listAllObservations();	
	    while (it.hasNext()) {
		observation = (Observation)it.next();
		v.add(observation.getName());
	    }
	}
	
	int nseq = v.size();

	Iterator is = v.iterator();
	while (is.hasNext()) {
	    String obsName = (String)is.next();
	    //observation = (Observation)it.next();
	    observation = group.findObservation(obsName);

	    countObs++;
	    //observation = (Observation)it.next();	   
	    
	    instConfig = observation.getInstrumentConfig();
	    inst       = TelescopeStatus.getInstrumentFor(instConfig);	

	    source      = observation.getSource();
	    target      = source.getPosition();
	  
	    // If this is NOT first obs. There IS a previous source.
	    if (countObs != 1) {
		if (source != prevSource) {
		    // New source. Add Slew time. (this is for nominal and maximal.
		    slewTime += target.getSlewTime(prevTarget, Math.toRadians(2.0), Math.toRadians(2.0));
		    countMoves++;
		}		
	    }
	    prevSource = source;
	    prevTarget = target;

	    // Add up all the contributions to this Obo's time.
	    
	    // Mosaics.
	    mosaic = 1;
	    switch (observation.getMosaic().getPattern()) {	   
	    case Mosaic.ARRAY:
		mosaic = observation.getMosaic().getCellsRA()*observation.getMosaic().getCellsDec();
		break;	 
	    case Mosaic.SINGLE:
		mosaic = 1;
		break;
	    case Mosaic.VERTICAL:
	    case Mosaic.HORIZONTAL:
	    case Mosaic.SLOPE_UP:
	    case Mosaic.SLOPE_DOWN:
		mosaic = 3;
		break;
	    case Mosaic.CROSS:	   
	    case Mosaic.X:
		mosaic = 5;	  
		break;
	    case Mosaic.HOLE:
		mosaic = 8;  
		break;
	    case Mosaic.GRID:
		mosaic = 9;  
		break;
	    default:
		mosaic = 1;
	    }
	
	    // Multruns.
	    multruns = observation.getNumRuns();
	   
	    // Maximal time for an observation.
	    maximalObserveTime = mosaic * 
		(Group.MAXIMAL_OFFSET_TIME + multruns * 
		 (observation.getExposeTime() + Group.MAXIMAL_READOUT_TIME + Group.MAXIMAL_DPRT_TIME));
	    
	    maximalExecutionTime += maximalObserveTime;
	    
	    // Nominal time for an observation.
	    nominalObserveTime = mosaic * 
		(Group.NOMINAL_OFFSET_TIME + multruns *
		 (observation.getExposeTime() + inst.calculateReadoutTime(instConfig)));
	    
	    //		 (observation.getExposeTime() + Group.NOMINAL_READOUT_TIME));
		 
	    nominalExecutionTime += nominalObserveTime;
		 
	} // per observation.
	
	// Total slew + moves * acquire + obs * config.
	nominalExecutionTime += slewTime + 
	    countMoves * Group.NOMINAL_ACQUIRE_TIME + 
	    countObs * Group.NOMINAL_CONFIGURATION_TIME;
	
	// total_slew + moves * acquire + obs * config + possible_unwrap + max_slew_to_next_source
	maximalExecutionTime += slewTime + 
	    countMoves * Group.MAXIMAL_ACQUIRE_TIME +
	    countObs * Group.MAXIMAL_CONFIGURATION_TIME +
	    Group.WRAP_TIME + Group.MAXIMAL_SLEW_TIME;
	
	System.err.println("Group: "+group.getFullPath()+
			   "\n\tMaximal exec time: "+(maximalExecutionTime/1000.0)+"S"+
			   "\n\tNominal exec time: "+(nominalExecutionTime/1000.0)+"S"+
			   "\n\tIntern slew time:  "+(slewTime/1000.0)+"S"+
			   "\n\tObs/seq:"+countObs+
			   "\n\tMoves:  "+countMoves);
	
	group.setNominalExecutionTime(nominalExecutionTime + 60000.0); // add 60 sec for slew plus settling ?
	group.setMaximalExecutionTime(maximalExecutionTime);
	group.setSlewTime(slewTime);
    }

    /** Initialize the scheduler. Caches the list (in sequence) of the next few
     * FixedGroups available.
     */
    public static void initialize() {

	
	

    }

    /** Get the next Fixed Group from the DB.
     * @param root DB Root,
     * @param minGap Minimum gap until start of group i.e. we ignore if too soon.
     * @return Next FixedGroup or null.
     */
    public static FixedGroup getNextFixedGroup(Root root, long minGap) {
	
	Tag      tag;
	
	User     user;
	
	Proposal proposal;
	
	Group    group;
	
	FixedGroup fg;
	
	FixedGroup nextGroup = null;
	
	// 200 years in the future.
	long now      = System.currentTimeMillis();
	long earliest = now + 200*365*86400*1000L;
	
	Iterator iTag = root.listAllTags();
	while (iTag.hasNext()) {
	    
	    tag = (Tag)iTag.next();
	    
	    Iterator iUser = tag.listAllUsers();
	    while (iUser.hasNext()) {
		
		user = (User)iUser.next();
		
		Iterator iProp = user.listAllProposals();
		while (iProp.hasNext()) {
	
		    KVPair pair = (KVPair)iProp.next();
		    String name = (String)pair.getKey();
		    Path   path = new Path(user.getFullPath(), name);
		    
		    try {
			proposal = OSS_Utilities.fetchProposal(path);	

			if (proposal == null) continue;
			
			System.err.println("Successfully Fetched Next Proposal: "+proposal.getName());
		   	
			
			System.err.println("Doing GetNextFixed Proposal: "+name);
			//System.err.println("Groups: "+proposal.getGroups());
			
			
			Iterator iGroup = proposal.listAllGroups();
			
			while (iGroup.hasNext()) {
			
			    group = (Group)iGroup.next();
			    
			    if (group instanceof FixedGroup) {	
				fg = (FixedGroup)group;
				if (fg.getFixedTime() < earliest &&
				    fg.getFixedTime() > now+minGap) {
				    nextGroup = fg;
				    earliest = fg.getFixedTime();
				}
			    }
			} // group
						
		    } catch (IOException iox) {
			System.err.println("Proposal fetch: Error: "+iox);	
			continue;
		    } catch (Exception e) {			
			System.err.println("Proposal fetch: General Exception: "+e);
			continue;
		    }	
		    
		} // next prop

	    } // next user

	} // next tag

	if (nextGroup != null)
	    System.err.println("Scheduling::Found NextFixed Group:: "+nextGroup.getFullPath()+
			       "Due at: "+sdf.format(new Date(nextGroup.getFixedTime()))+
			       " In "+((double)(nextGroup.getFixedTime()-now)/1000.0)+" secs");
	return nextGroup;
    }

    /** The one true scheduler....*/
    public static Scheduler scheduler = null;

    public static void setScheduler(Scheduler sched) {
	scheduler = sched;
    }
    
    /** Return the identified scheduler implementation.*/
    public static Scheduler getScheduler() {
	return scheduler;
    }

}


/** $Log: Scheduling.java,v $
/** Revision 1.6  2007/10/11 09:01:14  snf
/** removed spurious logging
/**
/** Revision 1.5  2007/02/27 11:47:22  snf
/** changed fixed group cache to ignore older than 1 hour
/**
/** Revision 1.4  2006/12/19 11:15:06  snf
/** Modified calcExec to add in the initial slew BUT NOT the settling time which is not detailed in Phase1 info.
/**
/** Revision 1.3  2006/12/15 12:11:15  snf
/** Checking fg stuff
/**
/** Revision 1.2  2006/12/05 10:39:25  snf
/** Changed calcExecTime for group to use sequence rather than obs list.
/**
/** Revision 1.1  2006/11/27 08:45:00  snf
/** Initial revision
/**
/** Revision 1.1  2006/11/27 08:37:05  snf
/** Initial revision
/**
/** Revision 1.4  2001/02/23 18:46:58  snf
/** Added stuff.
/**
/** Revision 1.3  2001/01/09 18:49:42  snf
/** Corrections.
/**
/** Revision 1.2  2000/11/22 14:52:38  snf
/** Added import to ngat.util.
/**
/** Revision 1.1  2000/11/22 14:13:01  snf
/** Initial revision
/** */



